package org.goldenport.g3.adapters

import java.sql.SQLSyntaxErrorException
import org.goldenport.wpath._
import org.goldenport.util.QSymbol
import org.goldenport.g3._
import org.goldenport.g3.messages._
import org.goldenport.g3.messages.sql.SqlInsert
import org.goldenport.g3.messages.datastore._
import org.goldenport.g3.messages.sql.SqlDatatype
import org.goldenport.g3.events.{NotFoundExceptioned, RecordNotValidatedExceptioned}
import org.goldenport.g3.events.datastore._
import org.goldenport.g3.events.sql._
import org.goldenport.g3.events.Exceptioned
import org.goldenport.g3.events.http._

/**
 * @since   Apr. 13, 2010
 * @version Jul.  3, 2011
 * @author  ASAMI, Tomoharu
 */
class DataStoreAdapter(
  // RecordClassSpace does not equal RecordCollectionSpace.
  // Currently RecordClassSpace represents own feature and RecordCollectionSpace.
  val recordClasses: RecordClassSpace = EmptyRecordClassSpace,
  val uri: String = "", val username: String = "",
  val password: String = "", val drivername: String = "",
  val driver: G3DataStoreDriver = null
) extends G3Adapter with ActivityActionable {
  private var _driver: Option[G3DataStoreDriver] = None

  override def open_Adapter() {
    if (driver != null) {
      _driver = Some(driver)
    } else {
      _driver = Some(context.createDataStoreDriver(
        uri, username, password, drivername))
    }
    _driver.get.open(context, recordClasses)
  }

  override def close_Adapter() {
    _driver.get.close()
  }

  def create_Activity(ctx: G3AgentContext) = _driver.get.createDriverActivity(ctx)

  // override protected def do_Action(content: AnyRef, parcel: G3Parcel, ctx: G3AgentContext): AnyRef = {
  //   content match {
  //     case cmd: DataStoreCommand => _driver.get.execute(cmd, ctx)
  //     case cmd: Post => _driver.get.execute(cmd, ctx)
  //   }
  // }
}

abstract class G3DataStoreDriver extends G3Driver {
  var recordClasses: RecordClassSpace = EmptyRecordClassSpace

  override protected def driver_Activity(ctx: G3AgentContext): PartialFunction[AnyRef, AnyRef] = {
    case cmd: DataStoreCommand => execute(cmd, ctx)
  }

  override final def open_Driver(params: List[AnyRef]) {
    params.headOption match {
      case Some(rcs: RecordClassSpace) => {
        recordClasses = rcs
        open_DataStoreDriver(params.tail)
      }
      case None => open_DataStoreDriver(params)
    }
  }

  protected def open_DataStoreDriver(params: AnyRef*) {
  }

  protected def new_Agent(cmd: G3Command, ctx: G3AgentContext): G3DataStoreDriverAgent
}

abstract class G3DataStoreDriverAgent(
  uri: String, ctx: G3AgentContext,
  recordClasses: RecordClassSpace
) extends G3DriverAgent(uri, ctx) {
  val rsctx = new G3AgentContextRecordContext(ctx)
  agent {
    case cmd: CreateKind => create(cmd)
    case cmd: CreateRecordClass => create(cmd)
    case cmd: FetchRecord => fetch(cmd)
    case cmd: QueryRecord => query(cmd)
    case cmd: QueryRecordExample => query(cmd)
    case cmd: QueryRecordExpression => query(cmd)
    case cmd: InsertRecord => insert(cmd)
    case cmd: InsertRecordSet => insert(cmd)
    case cmd: UpdateRecord => update(cmd)
    case cmd: UpdateRecordSet => update(cmd)
    case cmd: DeleteRecordId => delete(cmd)
    case cmd: DeleteRecordExample => delete(cmd)
    case cmd: DeleteRecordExpression => delete(cmd)
    case cmd: DropKind => drop(cmd)
  }

  def create(msg: CreateKind): G3Event
  def create(msg: CreateRecordClass): G3Event
  def fetch(msg: FetchRecord): G3Event
  def query(msg: QueryRecord): G3Event
  def query(msg: QueryRecordExample): G3Event
  def query(msg: QueryRecordExpression): G3Event
  def insert(msg: InsertRecord): G3Event
  def insert(msg: InsertRecordSet): G3Event
  def update(msg: UpdateRecord): G3Event
  def update(msg: UpdateRecordSet): G3Event
  def delete(msg: DeleteRecordId): G3Event
  def delete(msg: DeleteRecordExample): G3Event
  def delete(msg: DeleteRecordExpression): G3Event
  def drop(msg: DropKind): G3Event

  def get(msg: Get): G3Event = {
    try {
      if (msg.uri == null || msg.uri == "" || msg.uri == "/" || msg.uri == "/index") {
        new DataStoreManifestGetted("/", get_manifest(msg))
      } else {
        val result = get_records(msg)
        result match {
          case e: Exceptioned => e.toHttpStatusEvent
          case evt => evt
        }
      }
    } catch {
      case e: G3Exception => e.toHttpStatusEvent
    }
  }

  private def get_records(msg: Get): G3Event = {
    val path = WPath(msg.uri)
    val pc = path.createCursor
    val kindExpr = pc.next
    var isFetch = false
    var attribute: Option[String] = None
    val records: Either[G3Event, RecordSet] = if (pc.isEmpty) {
      val qs = new WQuery(start_index = Some(1), max_results = Some(100))
      query_Records(QSymbol(kindExpr.name), qs)
    } else {
      kindExpr.query match {
        case Some(q) => query_Records(QSymbol(kindExpr.name), q)
        case None => {
          val idExpr = pc.next
          attribute = idExpr.attribute
          idExpr.query match {
            case Some(q) => {
              if (idExpr.name == "") {
                query_Records(QSymbol(kindExpr.name), q)
              } else {
                Left(Http400(msg.uri))
              }
            }
            case None => {
              if (idExpr.name == "") {
                Left(Http400(msg.uri))
              } else {
                isFetch = true
                (fetch_Record(QSymbol(kindExpr.name), idExpr.name))
              }
            }
          }
        }
      }
    }
    records match {
      case Right(rs) => {
        if (pc.isEmpty) {
          if (attribute.isDefined) {
            val attr = attribute.get
            val values = rs.flatMap(_.get(Symbol(attr))).toList
            new DataStoreValueFetched(msg.uri, msg, values)
          } else if (isFetch) {
            new DataStoreFetched(msg.uri, msg, rs, true)
          } else {
            new DataStoreQueried(msg.uri, msg, rs)
          }
        } else {
          val records2 = retrieve_path(pc, records.right.get)
          attribute = pc.exprs.last.attribute
          if (attribute.isDefined) {
            val attr = attribute.get
            val x = for (r <- records2) yield {
              r
            }
            val xx = x.toList
            val values = records2.toList.flatMap(_.get(Symbol(attr)))
            new DataStoreValueFetched(msg.uri, msg, values)
          } else {
            new DataStoreQueried(msg.uri, msg, records2)
          }
        }
      }
      case Left(evt) => evt
    }
/*
    if (records.isRight) {
      if (pc.isEmpty) {
        if (isFetch) {
          new DataStoreFetched(msg.uri, msg, records.get.head, true)
        } else {
          new DataStoreQueried(msg.uri, msg, records.get)
        }
      } else {
        val records2 = retrieve_path(pc, records.right.get)
        new DataStoreQueried(msg.uri, msg, records2)
      }
    } else {
      records.left.get
    }
*/
  }

  // XXX recursive?
  private def retrieve_path_record(pc: WPathCursor, record: Record): List[RecordSet] = {
    if (pc.isEmpty) Nil
    else {
      val expr = pc.next
      val name = expr.name
      expr.query match {
        case Some(query) => {
          error("not implemented yet")
        }
        case None => {
          record.referenceIds(Symbol(name), rsctx) match {
            case Some((kind, ids)) => {
              fetch_Records(kind, ids) match {
                case r: Right[G3Event, RecordSet] => {
                  if (r.right.get.isEmpty) Nil
                  else List(r.right.get)
                }
                case l: Left[G3Event, RecordSet] => Nil
              }
            }
            case None => Nil
          }
        }
      }
    }
  }

  protected def fetch_Record(kind: QSymbol, id: Any): Either[G3Event, RecordSet] = {
    fetch_Records(kind, List(Record.normalizeId(id)))
  }

  protected def fetch_Records(kind: QSymbol, ids: List[Any]): Either[G3Event, RecordSet]

  protected def query_Records(kind: QSymbol, query: WQuery): Either[G3Event, RecordSet]

  private def retrieve_path(pc: WPathCursor, records: RecordSet): RecordSet = {
    if (pc.isEmpty) {
      records
    } else {
      new ConcatRecordSet(
        records.flatMap(retrieve_path_record(pc.duplicate, _)).toList)
    }
  }

  protected final def get_kind_schema(uri: QSymbol, provider: String): (String, Option[RecordSchema]) = {
    recordClasses.find(uri) match {
      case Some(rc) => (rc.getKind(provider), Some(rc.schema))
      case None => (uri.name, None)
    }
  }

  protected final def get_kind_idname_id(uri: QSymbol, provider: String): (String, Symbol, AnyRef) = {
    require (uri.length >= 2)
    val kindUri = uri.container
    val id = uri.leaf.name
    recordClasses.find(kindUri) match {
      case Some(rc) => {
        val idv = rc.schema.idField.normalize(id, rsctx).right.get // XXX left
        (rc.getKind(provider), rc.schema.idName, idv)
      }
      case None => (uri.name, 'id, id)
    }
  }

  protected final def get_kind_idname(uri: QSymbol, provider: String): (String, Symbol) = {
    recordClasses.find(uri) match {
      case Some(rc) => (rc.getKind(provider), rc.schema.idName)
      case None => (uri.name, 'id)
    }
  }

  protected final def get_kind(uri: QSymbol, provider: String): String = {
    recordClasses.find(uri) match {
      case Some(rc) => rc.getKind(provider)
      case None => uri.name
    }
  }

  protected final def get_idname(uri: QSymbol): Symbol = {
    recordClasses.find(uri) match {
      case Some(rc) => rc.schema.idName
      case None => 'id
    }
  }

  protected final def normalize_expression(uri: QSymbol, expr: Expression): Expression = {
    expr match {
      case id: Id => new Id(id.value, get_idname(uri))
      case _ => expr
    }
  }

  private def get_manifest(msg: Get) = {
    new DataStoreManifest(recordClasses)
  }
}

class SqlDataStoreDriver(val uri: String = "", val username: String = "", val password: String = "", val driver: String = "") extends G3DataStoreDriver with JdbcParameters {
  private lazy val sql_driver = new SqlDriver(uri, username, password, driver)

  override def open_DataStoreDriver(params: AnyRef*) {
    sql_driver.open(context)
  }

  override def close_Driver() {
    sql_driver.close()
  }

  protected def new_Agent(cmd: G3Command, ctx: G3AgentContext) = {
    new SqlDataStoreDriverAgent(uri, ctx, recordClasses, sql_driver)
  }
}

class SqlDataStoreDriverAgent(
  uri: String, 
  ctx: G3AgentContext,
  recordClasses: RecordClassSpace,
  val driver: SqlDriver
) extends G3DataStoreDriverAgent(uri, ctx, recordClasses) {
  private def make_sql_datatype(datatype: XDatatype,
                                multiplicity: Multiplicity): SqlDatatype = {
    context.makeSqlDataType(datatype, multiplicity)
  }

  private def make_ddl(schema: RecordSchema): sql.DDL = {
    def make_constraints(f: RecordField) = {
//      println("make_constraints: " + f.constraints)
      f.constraints.collect {
        case CId => sql.PRIMARY_KEY
        case CAutoId => sql.AUTO_INCREMENT
        case CUnique => sql.UNIQUE
      }
    }

    sql.DDL(schema.fields.map(f =>
      sql.Column(f.name,
                 make_sql_datatype(f.datatype, f.multiplicity),
                 make_constraints(f))
    ): _*)
  }

  def create(msg: CreateKind) = {
    recordClasses.find(msg.kind) match {
      case Some(rc) => create_record_class(rc)
      case None => new NotFoundExceptioned(msg)
    }
  }

  private def create_record_class(rc: RecordClass) = {
    val cmd = sql.Create(rc.getKind("sql"), make_ddl(rc.schema))
    driver.execute(cmd, context) match {
      case sql: SqlCreated => new DataStoreCreated(sql.uri) // XXX
      case other => other
    }
  }

  def create(msg: CreateRecordClass) = {
//    println("DataStore#create = " + msg.schema.fields.map(_.constraints))
    val cmd = sql.Create(msg.uri, make_ddl(msg.schema))
//    println("DataStore#create = " + cmd.ddl.columns.map(_.constraints))
    driver.execute(cmd, context) match {
      case sql: SqlCreated => new DataStoreCreated(sql.uri) // XXX
      case other => other
    }
  }

  def fetch(msg: FetchRecord) = {
/*
    println("DataStore#fetch = " + msg.ids.map {
      case ref: AnyRef => ref.getClass
      case v => "[" + v.toString + "]"
    })
*/
    val (kind, idname) = get_kind_idname(QSymbol(msg.uri), "sql")
    val cmd = sql.Select(kind, idname, msg.ids)
    driver.execute(cmd, context) match {
      case sql: SqlSelected => new DataStoreFetched(uri, msg, normalize_records(kind, sql.records), msg.ids.length == 1)
      case other => other
    }
  }

  private def normalize_records(kind: String, rs: RecordSet) = {
    recordClasses.findSchema(kind) match {
      case Some(s) => new RecordSchemaRecordSet(rs, s, rsctx)
      case _ => rs
    }
  }

  def query(msg: QueryRecord) = {
    val cmd = sql.Select(msg.query)
    driver.execute(cmd, context) match {
      case sql: SqlSelected => new DataStoreQueried(uri, msg, new RecordQueryRecordSet(sql.records, msg.query, context.context))
      case other => other
    }
  }

  def query(msg: QueryRecordExample) = {
    val (kind, idname) = get_kind_idname(QSymbol(msg.uri), "sql")
    val cmd = sql.Select(msg.uri, msg.record)
    try {
      driver.execute(cmd, context) match {
        case sql: SqlSelected => new DataStoreQueried(uri, msg, normalize_records(kind, sql.records))
        case other => other
      }
    } catch {
      case e: Exception => exception_in_query(e, msg)
    }
  }

  private def exception_in_query(e: Exception, msg: G3Command) = {
    e match {
      // derby: table has not been created yet.
      case e: SQLSyntaxErrorException => {
        // TODO warning
        new DataStoreQueried(uri, msg, RecordSet.empty)
      }
      case e => throw e
    }
  }

  def query(msg: QueryRecordExpression) = {
    val msguri = QSymbol(msg.uri)
    val (kind, idname) = get_kind_idname(msguri, "sql")
    val cmd = sql.Select(msg.uri, normalize_expression(msguri, msg.expression))
    driver.execute(cmd, context) match {
      case sql: SqlSelected => new DataStoreQueried(uri, msg, normalize_records(kind, sql.records))
      case other => other
    }
  }

  def insert(msg: InsertRecord) = {
    val (kind, idname) = get_kind_idname(QSymbol(msg.uri), "sql")
    val record = recordClasses.validate(QSymbol(msg.uri), msg.record, rsctx)
    if (record.isValid) {
      val cmd = sql.Insert(kind, record)
      driver.execute(cmd, context) match {
        case sql: SqlInserted => new DataStoreInserted(msg.uri, msg, RecordSet(msg.record), Some(sql))
        case other => other
      }
    } else {
      new RecordNotValidatedExceptioned(msg.uri, Some(msg), record)
    }
  }

  def insert(msg: InsertRecordSet) = {
    val kind = get_kind(QSymbol(msg.uri), "sql")
    val records = recordClasses.validate(QSymbol(msg.uri), msg.records, rsctx)
    if (records.isValid) {
      val cmd = sql.Insert(kind, records)
      driver.execute(cmd, context) match {
        case sql: SqlInserted => new DataStoreInserted(msg.uri, msg, msg.records, Some(sql))
        case other => other
      }
    } else {
      new RecordNotValidatedExceptioned(msg.uri, Some(msg), records.find(!_.isValid).get)
    }
  }

  def update(msg: UpdateRecord) = {
    val kind = get_kind(QSymbol(msg.uri), "sql")
    val record = recordClasses.validate(QSymbol(msg.uri), msg.record, rsctx)
    if (record.isValid) {
      val cmd = sql.Update(kind, record)
      driver.execute(cmd, context) match {
        case sql: SqlUpdated => new DataStoreUpdated(msg.uri, msg, RecordSet(msg.record), Some(sql))
        case other => other
      }
    } else {
      new RecordNotValidatedExceptioned(msg.uri, Some(msg), record)
    }
  }

  def update(msg: UpdateRecordSet) = {
    val kind = get_kind(QSymbol(msg.uri), "sql")
    val records = recordClasses.validate(QSymbol(msg.uri), msg.records, rsctx)
    if (records.isValid) {
      val cmd = sql.Update(kind, records)
      driver.execute(cmd, context) match {
        case sql: SqlUpdated => new DataStoreUpdated(msg.uri, msg, msg.records, Some(sql))
        case other => other
      }
    } else {
      new RecordNotValidatedExceptioned(msg.uri, Some(msg), records.find(!_.isValid).get)
    }
  }

  def delete(msg: DeleteRecordId) = {
    val (kind, idname) = get_kind_idname(QSymbol(msg.uri), "sql")
    val cmd = sql.Delete(kind, idname, msg.ids)
    driver.execute(cmd, context) match {
      case sql: SqlDeleted => new DataStoreDeleted(msg.uri, msg, Some(sql))
      case other => other
    }
  }

  def delete(msg: DeleteRecordExample) = {
    val kind = get_kind(QSymbol(msg.uri), "sql")
    val record = recordClasses.validate(QSymbol(msg.uri), msg.record, rsctx)
    val cmd = sql.Delete(kind, record)
    driver.execute(cmd, context) match {
      case sql: SqlDeleted => new DataStoreDeleted(msg.uri, msg, Some(sql))
      case other => other
    }
  }

  def delete(msg: DeleteRecordExpression) = {
    val kind = get_kind(QSymbol(msg.uri), "sql")
    val cmd = sql.Delete(kind, msg.expression)
    driver.execute(cmd, context) match {
      case sql: SqlDeleted => new DataStoreDeleted(msg.uri, msg, Some(sql))
      case other => other
    }
  }

  def drop(msg: DropKind) = {
    val cmd = sql.Drop(msg.uri)
    driver.execute(cmd, context) match {
      case sql: SqlDropped => new DataStoreDropped(sql.uri) // XXX
      case other => other
    }
  }

/*
  def get(msg: Get) = {
    val path = WPath(msg.uri)
    val pc = path.createCursor
    val kindExpr = pc.next
    val records = kindExpr.query match {
      case Some(q) => query_records(kindExpr.name, q)
      case None => {
        val idExpr = pc.next
        idExpr.query match {
          case Some(q) => {
            if (idExpr.name == "") {
              query_records(kindExpr.name, q)
            } else {
              throw new IllegalArgumentException(msg.uri)
            }
          }
          case None => {
            if (idExpr.name == "") {
              throw new IllegalArgumentException(msg.uri)
            } else {
              (get_record(kindExpr.name, idExpr.name))
            }
          }
        }
      }
    }
    if (records.isRight) {
      val records2 = retrieve_path(pc, records.right.get)
      new DataStoreQueried(msg.uri, msg, records2)
    } else {
      records.left.get
    }
  }

  private def get_record(kind: String, id: Any): Either[G3Event, RecordSet] = {
    get_records(kind, List(Record.normalizeId(id)))
  }

  private def get_records(kind: String, ids: List[Any]): Either[G3Event, RecordSet] = {
    val cmd = sql.Select(kind, idName, ids.map(Record.normalizeId))
    driver.execute(cmd, context) match {
      case sql: SqlSelected => Right(sql.records)
      case other => Left(other)
    }
  }

  private def query_records(kind: String, query: WQuery): Either[G3Event, RecordSet] = {
    val cmd = sql.Select(kind, make_query(query))
    driver.execute(cmd, context) match {
      case sql: SqlSelected => Right(sql.records)
      case other => Left(other)
    }
  }

  private def make_query(query: WQuery): Record = {
    Record(query.queries map {
      case (k, v) => Symbol(k) -> v
    })
  }

  private def retrieve_path(pc: WPathCursor, records: RecordSet): RecordSet = {
    if (pc.isEmpty) {
      records
    } else {
      new ConcatRecordSet(
        records.flatMap(retrieve_path_record(pc.duplicate, _)).toList)
    }
  }

  private def retrieve_path_record(pc: WPathCursor, record: Record): List[RecordSet] = {
    if (pc.isEmpty) Nil
    else {
      val expr = pc.next
      val name = expr.name
      expr.query match {
        case Some(query) => {
          error("not implemented yet")
        }
        case None => {
          record.referenceIds(name) match {
            case Some((kind, ids)) => {
              get_records(kind, ids) match {
                case r: Right[G3Event, RecordSet] => {
                  if (r.right.get.isEmpty) Nil
                  else List(r.right.get)
                }
                case l: Left[G3Event, RecordSet] => Nil
              }
            }
            case None => Nil
          }
        }
      }
    }
  }
*/

  protected def fetch_Records(uri: QSymbol, ids: List[Any]): Either[G3Event, RecordSet] = {
    val (kind, idname) = get_kind_idname(uri, "sql")
    val cmd = sql.Select(kind, idname, ids.map(Record.normalizeId))
    driver.execute(cmd, context) match {
      case sql: SqlSelected => Right(normalize_records(kind, sql.records))
      case event: SqlExceptioned => event.exception.get match {
        case e: SQLSyntaxErrorException => {
          Left(new NotFoundExceptioned(uri.path, exception = Some(e)))
        }
        case _ => Left(event)
      }
      case other => Left(other)
    }
  }

  protected def query_Records(uri: QSymbol, query: WQuery): Either[G3Event, RecordSet] = {
    def make_query(query: WQuery): Record = {
      Record(query.queries map {
        case (k, v) => Symbol(k) -> v
      })
    }

    val (kind, idname) = get_kind_idname(uri, "sql")
    val cmd = sql.Select(kind, make_query(query))
    driver.execute(cmd, context) match {
      case sql: SqlSelected => Right(normalize_records(kind, sql.records))
      case event: SqlExceptioned => event.exception.get match {
        case e: SQLSyntaxErrorException => {
          recordClasses.findSchema(uri) match {
            case Some(s) => Right(RecordSet.empty)
            case _ => Left(new NotFoundExceptioned(uri.path, exception = Some(e)))
          }
        }
        case _ => Left(event)
      }
      case other => Left(other)
    }
  }

  def post(msg: Post) = {
    val uri = QSymbol(msg.uri)
    val kind = get_kind(uri, "sql")
    val records = recordClasses.findSchema(uri) match {
      case Some(s) => recordClasses.validate(uri, msg.asRecords, rsctx)
      case None => normalize_rest_data(msg.asRecords)
    }
   if (records.isValid) {
     val cmd = sql.Insert(kind, records)
     driver.execute(cmd, context) match {
       case sql: SqlInserted => new DataStoreInserted(msg.uri, msg, msg.asRecords, Some(sql))
       case event: SqlExceptioned => event.exception.get match {
         case e: SQLSyntaxErrorException => create_post(uri, kind, cmd, msg, event)
         case _ => event
       }
       case other => other
     }
   } else {
      new RecordNotValidatedExceptioned(msg.uri, Some(msg), records.find(!_.isValid).get)
   }
  }

  private def create_post(uri: QSymbol, kind: String, cmd: SqlInsert, msg: Post, cause: G3Event) = {
    recordClasses.find(uri) match {
      case Some(c) => create_record_class(c) match {
        case dsc: DataStoreCreated => {
          driver.execute(cmd, context) match {
            case sql: SqlInserted => new DataStoreInserted(msg.uri, msg, msg.asRecords, Some(sql))
            case other => other
          }
        }
        case other => other
      }
      case _ => cause
    }
  }

  def put(msg: Put) = {
    if (msg.uri.contains('?')) {
      _put_query(msg)
    } else {
      _put_fetch(msg)
    }
  }

  private def _put_query(msg: Put) = {
    val path = WPath(msg.uri)
    val r = msg.asRecord
    if (r.isEmpty) {
      new NotFoundExceptioned(msg.uri, Some(msg), message = "no fields")
    } else {
      val tablename = path(0).name
      path(0).query match {
        case Some(query) => {
          val example = Record(query.queries)
          val cmd = sql.Update(tablename, example, r)
          driver.execute(cmd, context) match {
            case sql: SqlUpdated => new DataStoreUpdated(msg.uri, msg, msg.asRecords, Some(sql))
            case other => other
          }
        }
        case None => throw new IllegalArgumentException(msg.toString)
      }
    }
  }

  private def _put_fetch(msg: Put) = {
    val uri = QSymbol(msg.uri)
    val (kind, idname, id) = get_kind_idname_id(uri, "sql")
    val r = msg.asRecord
    if (r.isEmpty) {
      new NotFoundExceptioned(msg.uri, Some(msg), message = "no fields")
    } else {
      r += idname -> id
      val rs = new ListRecordSet(List(r))
      val rs2 = recordClasses.findSchema(uri) match {
        case Some(s) => recordClasses.validate(uri, rs, rsctx)
        case None => normalize_rest_data(rs)
      }
      // XXX
      val cmd = sql.Update(kind, rs2)
      driver.execute(cmd, context) match {
        case sql: SqlUpdated => new DataStoreUpdated(msg.uri, msg, msg.asRecords, Some(sql))
        case other => other
      }
    }
  }

  private def normalize_rest_data(records: RecordSet) = {
    new RestRecordSet(records.records)
  }

  override def delete(msg: Delete) = {
    if (msg.uri.contains('?')) {
      _delete_query(msg)
    } else {
      _delete_id(msg)
    }
  }
  
  private def _delete_query(msg: Delete) = {
    val path = WPath(msg.uri)
    val tablename = path(0).name
    path(0).query match {
      case Some(query) => {
        val example = Record(query.queries)
        val cmd = sql.Delete(tablename, example)
        driver.execute(cmd, context) match {
          case sql: SqlDeleted => new DataStoreDeleted(msg.uri, msg, Some(sql))
          case other => other
        }
      }
      case None => throw new IllegalArgumentException(msg.toString)
    }
  }    

  private def _delete_id(msg: Delete) = {
    val (kind, idname, id) = get_kind_idname_id(QSymbol(msg.uri), "sql")
    val cmd = sql.Delete(kind, idname, id)
    driver.execute(cmd, context) match {
      case sql: SqlDeleted => new DataStoreDeleted(msg.uri, msg, Some(sql))
      case other => other
    }
  }

  def mkcol(msg: Mkcol) = {
    error("XXX")
  }
}
// class SqlDataStoreRecordSet 2010-09-28
